// Created: Dec 14, 2014
//
// Author: wjinwen.1988@gmail.com

#include "tcpclient.h"

#include <stdio.h>  // snprintf()
#include <assert.h>

#include <functional>

#include <glog/logging.h>

#include "chi/net/socket.h"
#include "chi/net/event_loop.h"
#include "chi/net/connector.h"

namespace chi {
namespace net {

using namespace std::placeholders;

namespace detail {
  void RemoveConnection(EventLoop* loop, const TcpConnectionPtr& conn) {
  loop->RunInLoop(std::bind(&TcpConnection::ConnectDestroyed, conn));
}
}

TcpClient::TcpClient(EventLoop* loop, 
                     const InetAddress& server_addr, 
                     const std::string& name)
  : loop_(loop),
    connector_(new Connector(loop, server_addr)),
    name_(name),
    connection_cb_(DefaultConnectionCallback),
    message_cb_(DefaultMessageCallback),
    retry_(false),
    connect_(false),
    next_connid_(1),
    connection_(nullptr) {
  connector_->set_new_conn_cb(
      std::bind(&TcpClient::NewConnection, this, _1));
  VLOG(11) << "TcpClient::TcpClient(this=" << this << "): ctor";
}

TcpClient::~TcpClient() {
  VLOG(11) << "TcpClient::~TcpClient(this=" << this << "): dtor";

  TcpConnectionPtr conn;
  {
    MutexLockGuard lock(mutex_);
    conn = connection_;
  }
  if (conn) {
    CloseCallback cb = std::bind(detail::RemoveConnection, loop_, _1);
    loop_->RunInLoop(std::bind(&TcpConnection::set_close_callback, conn, cb));
  } else {
    connector_->Stop();
  }
}

void TcpClient::Connect() {
  LOG(INFO) << connector_->server_addr().ToIpPort() 
            << " - connecting to " << name_;

  connect_ = true;
  connector_->Start();
}

void TcpClient::Disconnect() {
  connect_ = false;
  
  if (connection_) {
    connection_->Shutdown();
  }
}

void TcpClient::Stop() {
  connect_ = false;
}

void TcpClient::set_connection_callback(const ConnectionCallback& cb) {
  connection_cb_ = cb;
}

void TcpClient::set_message_callback(const MessageCallback& cb) {
  message_cb_ = cb;
}

void TcpClient::NewConnection(int sockfd) {
  InetAddress peeraddr(Socket::GetPeerAddr(sockfd)); 
  char buf[32];
  snprintf(buf, sizeof(buf), ":%s#%d", 
           peeraddr.ToIpPort().c_str(), next_connid_);
  ++next_connid_;
  std::string conn_name = name_ + buf;

  InetAddress localaddr(Socket::GetLocalAddr(sockfd));
  TcpConnectionPtr conn(new TcpConnection(loop_, 
                                          conn_name,
                                          sockfd, 
                                          localaddr,
                                          peeraddr));
  conn->set_connection_callback(connection_cb_);
  conn->set_message_callback(message_cb_);
  conn->set_close_callback(
      std::bind(&TcpClient::RemoveConnection, this, _1));
  assert(connection_ == nullptr);
  {
    MutexLockGuard lock(mutex_);
    connection_ = conn;
  }
  connection_->ConnectionEstablished();
}

void TcpClient::RemoveConnection(const TcpConnectionPtr& conn) {
  assert(conn == connection_);
  assert(conn->loop() == loop_);

  {
    MutexLockGuard lock(mutex_);
    connection_.reset();
  }
  loop_->QueueInLoop(std::bind(&TcpConnection::ConnectDestroyed, conn));
  if (retry_ && connect_) {
    LOG(INFO) << connector_->server_addr().ToIpPort() <<  " - reconnecting to " << name_;
    connector_->Restart();
  }
}

}
} 
